/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 *
 * \file
 * \brief MaCI Process launcher header.
 * \author Antti Maula <antti.maula@tkk.fi>
 *
 */
#ifndef _MACI_LAUNCHER_EXECUTOR_HPP_
#define _MACI_LAUNCHER_EXECUTOR_HPP_
#include <vector>
#include <string>
#include "launchertypes.hpp"

/** Execute an application as independent process.
 */
class CExecutor
{
public:
  CExecutor();
  ~CExecutor();


  /** Create IO Pipe.
   *
   * Creates an Input/Output pipe. The pipe is connected to stdin and
   * stdout of the forked process. (Calls SetInputHandle() and
   * SetOutputHandle() functions)
   *
   * \note This function must be called \b before the Execute
   * function, otherwise the call will have no effect.
   *
   * @return                    'true' when pipe was succesfully created,
   *                            'false when creating pipe failed.
   */
  bool CreateIOPipe();


  /** Read from IO Pipe.
   * 
   * Reads from the Pipe created with 'CreateIOPipe()'. If the
   * pipe doesn't exists, this function returns error.
   * Semantics of the call matches the standard POSIX 'read()'.
   *
   * @param[out] aBuffer        Pointer to store bytes read
   * @param[in] aSize           Maximum size of buffer available
   * @return                    Number of bytes read, or negative on error.
   */
  int ReadIOPipe(void *aBuffer, size_t aSize);


  /** Write to IO Pipe.
   *
   * Writes to the Pipe created with 'CreateIOPipe()'. If the
   * pipe doesn't exists, this function returns error.
   * Semantics of the call matches the standard POSIX 'write()'.
   *
   * @param[out] aBuffer        Pointer to read bytes from
   * @param[in] aSize           Size of buffer
   * @return                    Number of bytes written, or negative on error.
   */
  int WriteIOPipe(const void *aBuffer, size_t aSize);


  /** Waits for input to be available on the pipe.
   *
   * This function enters the thread in sleep until new data is
   * available on the pipe, or timeout occurs. If this function
   * returns with positive value (> 0), a following ReadIOPipe() call
   * will not block.
   *
   * @param[in] aTimeout_ms Timeout in milliseconds to wait for any
   *                        input. The function will return immediately when
   *                        any input is available.
   * @return                Negative values on error (No read pipe available or
   *                        poll() syscall failed)\n
   *                        Zero on timeout (no data available)\n
   *                        Positive value when new data is available. 
   *                        (Calling 'ReadIOPipe()' will not block)
   */
  int WaitReadIOPipe(const int aTimeout_ms);


  /** Waits for output stream to became free.
   *
   * This function enters the thread in sleep until the output stream
   * is ready to receive new data, or timeout occurs. If this function
   * returns with positive value (> 0), a following WriteIOPipe() call
   * will not block.
   *
   * @param[in] aTimeout_ms Timeout in milliseconds to wait. 
   * @return                Negative values on error (No write pipe available or
   *                        poll() syscall failed)\n
   *                        Zero on timeout (no space available)\n
   *                        Positive value when space is available for writing. 
   *                        (Calling 'WriteIOPipe()' will not block)
   */
  int WaitWriteIOPipe(const int aTimeout_ms);


  /** Set stdin redirection handle.
   * 
   * This functions sets the file descriptor to be used for 'stdin' of
   * the Executed process. If the 'CreateIOPipe()' function is called,
   * this handle is already set, so resetting it will break things.
   *
   * \note This function must be called \b before the Execute
   * function, otherwise the call will have no effect.
   *
   * @param[in] aHandle         File descriptor to set
   */
  inline void SetInputHandle(const int aHandle);


  /** Set stdout redirection handle.
   * 
   * This functions sets the file descriptor to be used for 'stdout' of
   * the Executed process. If the 'CreateIOPipe()' function is called,
   * this handle is already set, so resetting it will break things.
   *
   * \note This function must be called \b before the Execute
   * function, otherwise the call will have no effect.
   *
   * @param[in] aHandle         File descriptor to set
   */
  inline void SetOutputHandle(const int aHandle);


  /** Set stderr redirection handle.
   * 
   * This functions sets the file descriptor to be used for 'stderr' of
   * the Executed process. If the 'CreateIOPipe()' function is called,
   * this handle is already set, so resetting it will break things.
   *
   * \note This function must be called \b before the Execute
   * function, otherwise the call will have no effect.
   *
   * @param[in] aHandle         File descriptor to set
   */
  inline void SetErrorHandle(const int aHandle);


  /** Execute a process.
   *
   * Executes a command as a child process. This function forks() and 
   * execv():s the new process, but retains control by storing the
   * new identifier to this class. 
   *
   * @param[in] aCommand        Command to execute (must be absolute path)
   * @param[in] aParameters     Array of parameters to be passed to newly created
   * @param[in] aDelay_ms       Time to wait in milliseconds before this process
   *                            is actually launcher. (This function returns immediately
   *                            anyhow) Defaults to zero (Start immediately)
   *                            process. The default array is empty (no parameters)
   * @param[in] aWorkDir        Change to this working directory before launching.
   *                            If this is empty (length == 0), no change will be done.
   * @param[in] aSetUID         If this is specified, the child process will attempt
   *                            to change to this user id before executing binary.
   * @param[in] aSetGID         If this is specified, the child process will attempt
   *                            to change to this group id before executing binary.
   * @return                    'true' when the process was succesfully executed,
   *                            'false' when some part of the execution failed.
   */
  bool Execute(const std::string &aCommand,
               const TStringArray &aParameters = TStringArray(),
               const unsigned int aDelay_ms = 0,
               const std::string &aWorkDir = "",
               const int aSetUID = -1,
               const int aSetGID = -1);

  
  /** Re-execute process with same parameters as before.
   * 
   * This function calls the Execute with same parameters as on
   * previous succesfull call.
   *
   * @param[in] aDelay_ms_add   Add this value to delay value when starting,
   *                            resulting value will NOT be stored. The effect is
   *                            that module will sleep 'aDelay_ms_add + module_delay_ms'
   *                            before really executing.
   * @return                    'true' when the process was succesfully executed,
   *                            'false' when some part of the execution failed.
   */
  bool ReExecute(const unsigned int aDelay_ms_add = 0);


  /** Wait for process to terminate.
   *
   * This function will block until the associated active process has terminated.
   * 
   * @param[out] aResult        Result data from the process will
   *                            be stored here. This is NOT the pure return value
   *                            of the process. The value is the 'status' value as
   *                            returned by the 'waitpid()' function. (See 'man waitpid'
   *                            for details)
   * @param[in] aBlock          Select blocking operation. If this is 'false', the function
   *                            will return immediately if the process has not yet
   *                            terminated. Application should inspect the result value
   *                            to determine the real state of the process.
   * @return                    
   */
  int Wait(int &aResult, bool aBlock = true);


  /** \overload int Wait(void)
   *
   * This function calls the Wait(int &aResult, bool aBlock = true)
   * function with local 'aResult' and aBlock='true' default values,
   * and returns the value stored by the Wait(int &aResult...) in the
   * aResult field.
   * 
   * @return                    Value stored by the 'Wait(int &aResult, bool aBlock = true)'
   *                            int the 'aResult' field.
   */
  int Wait(void) { int x; return Wait(x, true); }
  

  /** Send KILL signal to process.
   *
   * This function sends a KILL signal to the previously started process.
   * 
   * @return                    'true' when the KILL signal was succesfully delivered
   *                            on the process. 'false' when signal delivery failed.
   */
  bool Kill(void);


  /** Send TERM signal to process.
   *
   * This function sends a TERM signal to the previously started process.
   * 
   * @return                    'true' when the TERM signal was succesfully delivered
   *                            on the process. 'false' when signal delivery failed.
   */
  bool Terminate(void);


  /** Detach from process.
   *
   * This function detaches the Executor from the current
   * Process. This has the effect that the process stays running
   * alone, and cannot the controlled by the parent application
   * anymore. Use this with caution, as it has the side effect of
   * leaving zombie proceses lying around.
   *
   * @return                   'true' when succesfully detached,\n 
   *                           'false' on any error (no process active, etc.)
   */
  bool Detach(void);
  

  /** Get executed process ID.
   * 
   * Returns the Process Identifier of the process running as child.
   *
   * @return                    Process identifier of the child process, or
   *                            negative on error.
   */
  inline int GetProcessID(void) const;


private:
  // Functions
  static char **CreatePtrArray(const std::string &aCommand, const TStringArray &aArray);
  static void DestroyPtrArray(char **aArray, const int aElements);
  static void RedirectIO(const int aNewIn, const int aNewOut, const int aNewErr);

  // Variables
  int iProcessID;
  int iInputHandle;
  int iOutputHandle;
  int iErrorHandle;
  int iPipeRead;
  int iPipeWrite;

  // Stored input to Execute.
  std::string iCommand;
  TStringArray iParameters;
  unsigned int iDelay_ms;
  std::string iWorkDir;
  int iSetUID;
  int iSetGID;
};


// Inline functions
inline int CExecutor::GetProcessID(void) const {
  return iProcessID;
}
inline void CExecutor::SetInputHandle(const int aHandle)
{
  iInputHandle = aHandle;
}
inline void CExecutor::SetOutputHandle(const int aHandle)
{
  iOutputHandle = aHandle;
}
inline void CExecutor::SetErrorHandle(const int aHandle)
{
  iErrorHandle = aHandle;
}

#endif
